﻿package cz.dadul.adventure.view
{
	/**
	 * ...
	 * @author Dadul
	 */
	import flash.display.*;
	import flash.events.*;
	import cz.dadul.adventure.model.*;
	import cz.dadul.adventure.model.events.*;
	import cz.dadul.adventure.controller.*;
	 
	 public class LayerController extends Sprite
	{
		// Public Constants
		public static const MAX_LAYERS:int = 10;		
		public static const AUTO_PARALLAX_FACTOR:Number = -1; // Means automatic parallaxing
		// Private constants
		private static const defaultLayerName:String = "layer";
		
		// Singleton instance
		private static var instance:LayerController;

		// Layers
		private var _layers:Array;
		private var _mainLayer:MainLayer;
		private var _elements:Array;
		
		// Other pointers
		private var _storage:Storage;
		
		// Placeholders;
		// All the layers including mainLayer
		private var _layersPlaceholder:Sprite;
		// Special layer masking effects
		private var _maskPlaceholder:Sprite;
		// Storage for all PickableItems
		private var _storagePlaceholder:Sprite;
		// Game menus etc.
		private var _controlsPlaceholder:Sprite;
		// Mouse pointer
		private var _mousePlaceholder:Sprite;
		
		private var _stageWidth:uint;
		private var _stageHeight:uint;
		private var _stageCenter:uint;
				
		public function LayerController(p_key:SingletonBlocker):void{
			// this shouldn't be necessary unless they fake out the compiler:
			 if (p_key == null) {
				throw new Error("Error: Instantiation failed: Use LayerController.getInstance() instead of new.");
			  }
			  init();
		}
			      
		public static function getInstance():LayerController {
         if (instance == null) {
            instance = new LayerController(new SingletonBlocker());
          }
         return instance;
		}
		
		
		public function initStage(stageWidth: int, stageHeight: int):void {
			this._stageWidth = stageWidth;
			this._stageHeight = stageHeight;
			this._stageCenter = Math.floor(stageWidth / 2);
			
			// Parallax pre-calculations
			for (var i:int = 0; i < _layersPlaceholder.numChildren; i++) {
				var layerName:String = _layersPlaceholder.getChildAt(i).name;
				
				if (_layers[layerName].layer.width > _stageWidth) {
					
					// Calculate only auto-parallax factors
					if (_layers[layerName].parallaxFactor == AUTO_PARALLAX_FACTOR) {
						
						if (_layers[layerName].layer == _mainLayer) {
							// Main layer 
							_layers[layerName].parallaxFactor = 1;
						} else {
							// Other layers
							_layers[layerName].parallaxFactor = (_layers[layerName].layer.width- _stageWidth) / (_mainLayer.width - _stageWidth);
						}
						
					}
				} else {
					_layers[layerName].layer.scrollEnabled = false;
				}
				
				
			}
		}
		
	
		public function addLayer(layer:Layer, mainLayer:Boolean = false,x:int = 0, y:int = 0, parallaxFactor:Number = AUTO_PARALLAX_FACTOR) : Layer {
			var layerName:String = layer.name;
			//-----------------------------------
			if (this._layersPlaceholder.numChildren > MAX_LAYERS) {
				throw new Error("Maximum number of layers reached!");	
			}
			
			// Coordination
			layer.x = x;
			layer.y = y;
	
			
			if (mainLayer) {
				if (this._mainLayer != null) throw new Error("The main layer is already defined!");	
				this._mainLayer = layer as MainLayer;
			}
			
			// Add to the array
			this._layers[layerName] = new LayerItem(layer, layer.x, layer.y, parallaxFactor);
			
			// Add to the stage
			this._layersPlaceholder.addChild(layer);
			
			return layer;		
		}
		

		public function addPlayer(p:Player, x:int = 0, y:int = 0):Player {
			// Adds the player to the main layer
						
			return this._mainLayer.addPlayer(p, x, y);
		}
		
		/**
		 * 
		 * Adds an item to the layer
		 * 
		 * @param	el
		 * @param	elName
		 * @param	layerName
		 * @param	x
		 * @param	y
		 * @return
		 */		
		public function addItem(el:Item, layerName :String, x:int = 0, y:int = 0):Item {
			var elName:String = el.name;
			//------------------------------
			// Checks layer name
			if (this._layers[layerName].layer == undefined) {
				throw new Error("Layer ["+ layerName+"] is not defined yet!");	
			}						
			// Checks if it is not the main layer
			if (this._layers[layerName].layer == this._mainLayer) {
				throw new Error("You can not use this function for the main layer!");	
			}									
			// Checks element uniqueness
			checkElement(el, elName);
			// Adds to the main pool
			this._elements[elName] = new ElementItem(el, layerName);
			// Add to the layer
			return this._layers[layerName].layer.addItem(el, x, y, Layer.STANDARD_LAYER);
		}
		
		/**
		 * 
		 * Adds an item to the main layer
		 * 
		 * @param	el
		 * @param	elName
		 * @param	mainLayerType
		 * @param	x
		 * @param	y
		 * @return
		 */
		public function addMainItem(el:Item, x:int = 0, y:int = 0,  mainLayerType: int = Layer.STANDARD_LAYER):Item {
			var elName:String = el.name;
			//------------------------------
			if (this._mainLayer == null) {
				throw new Error("The main layer is not defined yet!");	
			}
			// Checks element uniqueness
			checkElement(el, elName);
			// Adds to the main pool
			this._elements[elName] = new ElementItem(el, this._mainLayer.name);
			// Add to the main layer 
			return this._mainLayer.addItem(el, x, y, mainLayerType);
		}
					
		
		public function addStorage(storage:Storage):Storage {
			if (this._storagePlaceholder.numChildren > 0) throw new Error("Storage has been already added!");			
			this._storagePlaceholder.addChild(storage);
			this._storage = storage;
			return storage;
		}
		
		
		
		/**
		 * Function triggered on every player's movement
		 * e.data	-	Instance of the 'player' object
		 * 
		 * @param	e	Data given by the caller
		 */	
		public function updatelayers(e: DataHolderEvent):void {
			// Check items z-index
			this._mainLayer.checkDepths(e.data);			
			// Compute parallax offset
			performParallax(e.data);
		}
		
		public function removeMainItem(item: Item):void {
			// Remove item from the main layer (*ONLY* from the main menu)
			try {
				removeItem(item);
			}
			catch (e: Error){
				throw new Error("Item ["+item.name+"] can not be removed from other than MainLayer!");
			}
		}

		public function removeItem(item: Item):void {		
			if (!_layers[_elements[item.name].layerName].layer.removeItem(item)) {
				throw new Error("Internal error during removing the item ["+item.name+"]!");
			} else {
				// Remove from the array
				_elements[item.name] = null;
			}
		}
		
		
		public function getElement(elName:String):Element {
			if(this._elements[elName] == null){
				throw new Error("Element [" + elName +"] has not been found!");	
			}		
			return this._elements[elName].element;
		}

		/**
		 * PRIVATE 
		 */			
		
		/**
		 * Basic initialization
		 */		 
		private function init(): void {
			this._layers = [];
			this._elements = [];
			// Add placeholders
			_layersPlaceholder = new Sprite();
			_maskPlaceholder = new Sprite();
			_storagePlaceholder = new Sprite();
			_controlsPlaceholder = new Sprite();
			_mousePlaceholder = new Sprite();
			addChild(_layersPlaceholder);
			addChild(_maskPlaceholder);
			addChild(_storagePlaceholder);
			addChild(_controlsPlaceholder);
			addChild(_mousePlaceholder);
		}
		

		private function performParallax(player:Player):void {
			// Player's offset
			var dx:int = player.x - this._stageCenter;			
			var pdx:int;
						
			// Main stage
			for (var i:int = 0; i < _layersPlaceholder.numChildren; i++) {
				var layerName:String = _layersPlaceholder.getChildAt(i).name;
				// Perform only on scrolled enabled layers
				
				if (_layers[layerName].layer.scrollEnabled) {
					
						

					pdx = Math.floor((dx * _layers[layerName].parallaxFactor));
					
					
					if ((_layers[layerName].layer.width - _stageWidth - pdx >= 0) && (_layers[layerName].origX  + pdx >= 0)  )   {
											
						_layers[layerName].layer.x = _layers[layerName].origX - pdx;
					} else {
						// Limit borders
						if (_layers[layerName].origX  + pdx < 0) _layers[layerName].layer.x = _layers[layerName].origX;
						if (_layers[layerName].layer.width - _stageWidth - pdx < 0) _layers[layerName].layer.x = _layers[layerName].origX - (_layers[layerName].layer.width - _stageWidth);
						
						
					}
					
					
				}
			}
		}			
	
		private function checkElement(el:Element, elName:String):void {
			// Checks element instance
			if (this._elements[el.name] != undefined) {
				throw new Error("Instance of element ["+ elName+"] already exists!");	
			}
			// Checks element name 
			if (this._elements[elName] != undefined) {
				throw new Error("Element name ["+ elName+"] already in use!");	
			}			
		}
					
		
		/**
		 * SETTERS / GETTERS
		 */
	   
	
			
		
	}
	
}
/**
 * INTERAL CLASSES
 */

import cz.dadul.adventure.model.*;

class LayerItem {
	private var _layer:Layer;
	private var _parallaxFactor:Number;
	private var _x:int;
	private var _y:int;
	
	public function LayerItem(l: Layer, x: int, y: int, pF: int) {
		_x = x;
		_y = y;
		_layer = l;
		_parallaxFactor = pF;
	}
	
	public function get layer():Layer {
		return _layer;
	}
	public function get origX():int {
		return _x;
	}
	public function get origY():int {
		return _y;
	}	
	public function get parallaxFactor():Number {
		return _parallaxFactor;
	}
	public function set parallaxFactor(pF:Number):void {
		_parallaxFactor = pF;
	}

	
}

class ElementItem {
	private var _layerName:String;
	private var _element:Element;
	
	public function ElementItem(el: Element, ln: String) {
		_layerName = ln;
		_element = el;
	}
	
	public function get layerName():String {
		return _layerName;
	}
	public function get element():Element {
		return _element;
	}
}

internal class SingletonBlocker {}